home *** CD-ROM | disk | FTP | other *** search
/ SGI Freeware 2002 November / SGI Freeware 2002 November - Disc 1.iso / dist / fw_exmh.idb / usr / freeware / lib / exmh-2.5 / html_dpackage.tcl.z / html_dpackage.tcl
Text File  |  2002-07-08  |  32KB  |  1,190 lines

  1. # Simple HTML display library by Stephen Uhler (stephen.uhler@sun.com)
  2. # Copyright (c) 1995 by Sun Microsystems
  3. # Version 0.3 Thu Aug 31 14:11:29 PDT 1995
  4. #
  5. # See the file "license.terms" for information on usage and redistribution
  6. # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  7. #
  8. # To use this package,  create a text widget (say, .text)
  9. # and set a variable full of html, (say $html), and issue:
  10. #    HMinit_win .text
  11. #    HMparse_html $html "HMrender .text"
  12. # You also need to supply the routine:
  13. #   proc HMlink_callback {win href} { ...}
  14. #      win:  The name of the text widget
  15. #      href  The name of the link
  16. # which will be called anytime the user "clicks" on a link.
  17. # The supplied version just prints the link to stdout.
  18. # In addition, if you wish to use embedded images, you will need to write
  19. #   proc HMset_image {handle src}
  20. #      handle  an arbitrary handle (not really)
  21. #      src     The name of the image
  22. # Which calls
  23. #    HMgot_image $handle $image
  24. # with the TK image.
  25. #
  26. # To return a "used" text widget to its initialized state, call:
  27. #   HMreset_win .text
  28. # See "sample.tcl" for sample usage
  29. ##################################################################
  30. ############################################
  31. # mapping of html tags to text tag properties
  32. # properties beginning with "T" map directly to text tags
  33.  
  34. # These are Defined in HTML 2.0
  35.  
  36. array set HMtag_map {
  37.     address {style i}
  38.     b      {weight bold}
  39.     big    {size 5}
  40.     blockquote    {indent 1 Trindent rindent}
  41.     bq        {indent 1 Trindent rindent}
  42.     cite   {style i}
  43.     code   {size 3 family courier}
  44.     dfn    {style i}    
  45.     dir    {indent 1}
  46.     dl     {indent 1}
  47.     em     {style i}
  48.     h1     {size 6 weight bold Tspace hspacebig family times}
  49.     h2     {size 5 weight bold Tspace hspacebig family times}        
  50.     h3     {size 4 weight bold Tspace hspacemid family times}    
  51.     h4     {size 3 weight bold Tspace hspacemid family times}
  52.     h5     {size 2 weight bold Tspace hspacemid family times}
  53.     h6     {size 1 Tspace hspacesml family times}
  54.     i      {style i}
  55.     kbd    {family courier weight bold}
  56.     menu     {indent 1}
  57.     ol     {indent 1}
  58.     pre    {fill 0 family courier size 3 Tnowrap nowrap}
  59.     samp   {size 3 family courier}        
  60.     small {size 2}
  61.     strong {weight bold}
  62.     sup    {size 2 Tsup sup}
  63.     sub    {size 2 Tsup sub}
  64.     tt     {size 3 family courier}
  65.     u    {Tunderline underline}
  66.     ul     {indent 1}
  67.     var    {style i}    
  68. }
  69.  
  70. # This correspond to the Netscape font sizes 1 to 7
  71.  
  72. array set HMsize_map {
  73.     "" ""
  74.     1 10
  75.     2 12
  76.     3 14
  77.     4 18
  78.     5 20
  79.     6 24
  80.     7 36
  81. }
  82.  
  83. # These are in common(?) use, but not defined in html2.0
  84.  
  85. array set HMtag_map {
  86.     center {Tcenter center}
  87.     strike {Tstrike strike}
  88.     u    {Tunderline underline}
  89. }
  90.  
  91. # initial values
  92.  
  93. set HMtag_map(hmstart) {
  94.     family times   weight medium   style r   size 3
  95.     Tcenter ""   Tlink ""   Tnowrap ""   Tunderline ""   list list
  96.     fill 1   indent "" counter 0 adjust 0
  97.     Tspace hspacep
  98. }
  99. proc HMdefault_font {win} {
  100.     upvar #0 HM$win var
  101.     global HMsize_map
  102.     HMx_font times $HMsize_map(3) medium r $var(S_adjust_size)
  103. }
  104.  
  105. ############################################
  106. # initialize the window and stack state
  107.  
  108. proc HMinit_win {win} {
  109.     global window
  110.     upvar #0 HM$win var
  111.     
  112.     HMinit_state $win
  113.     $win tag configure underline -underline 1
  114.     $win tag configure center -justify center
  115.     $win tag configure left -justify left
  116.     $win tag configure right -justify right
  117.     $win tag configure nowrap -wrap none
  118.     $win tag configure rindent -rmargin $var(S_tab)c
  119.     $win tag configure strike -overstrike 1
  120.     $win tag configure mark -foreground red            ;# list markers
  121.     if {$window(colorAnchor)} {
  122.         $win tag configure anchor -foreground purple
  123.     }
  124.     $win tag configure HTML -foreground #880 -background #d9d9d9    
  125.     $win tag configure SHTML -foreground #800 -background #d9d9d9 \
  126.         -relief ridge -borderwidth 2
  127.  
  128.     # Restore foreground/background 'cause <Body> tag can change it.
  129.     $win config -background [lindex [$win config -background] 3]
  130.     $win config -foreground [lindex [$win config -foreground] 3]
  131.  
  132.     $win tag configure hspacebig -spacing1 10p -spacing3 6p
  133.     $win tag configure hspacemid -spacing1 6p -spacing3 3p
  134.     $win tag configure hspacesml -spacing1 3p -spacing3 3p
  135.     $win tag configure hspacep -spacing1 3p -spacing3 3p
  136.     $win tag configure compact -spacing1 1p -spacing3 0p
  137.     $win tag configure abovebr -spacing3 0p
  138.     $win tag configure belowbr -spacing1 0p
  139.  
  140.     $win tag configure sup -offset 3    ;# superscript
  141.     $win tag configure sub -offset -3    ;# subscript
  142.  
  143.  
  144.     $win tag bind link <Button-1> [list Url_Hit? $win %x %y]
  145.     $win tag bind link <Shift-Button-1> [list Url_HitNew $win %x %y]
  146.     $win tag bind link <Double-Button-1> [list Url_Edit $win %x %y]
  147.  
  148.     HMset_indent $win $var(S_tab)
  149.     $win configure -wrap word
  150.  
  151.     # configure the text insertion point
  152.     $win mark set $var(S_insert) 1.0
  153.  
  154.     # for horizontal rules
  155.     Hr_Init $win
  156.  
  157.     bind $win <Configure> {
  158.         %W tag configure last -spacing3 %h
  159.     }
  160.     if {0} {
  161.         # These are already on the exmh window
  162.  
  163.         bind TScroll <Key-Next> {%W yview scroll 1 page}
  164.         bind TScroll <Key-Prior> {%W yview scroll -1 page}
  165.         bind TScroll <Key-Home> {%W see 1.0}
  166.         bind TScroll <Key-End> {%W see end}
  167.     }
  168.  
  169. }
  170.  
  171. # set the indent spacing (in cm) for lists
  172. # TK uses a "weird" tabbing model that causes \t to insert a single
  173. # space if the current line position is past the tab setting
  174.  
  175. proc HMset_indent {win cm} {
  176.     $win configure -tabs [expr $cm / 2.0]c
  177.     foreach i {1 2 3 4 5 6 7 8 9} {
  178.     set indent [expr $i * $cm]
  179.     $win tag configure indent$i -lmargin1 ${indent}c -lmargin2 ${indent}c \
  180.         -tabs "[expr $indent + $cm/2.0]c [expr $indent + $cm]c" \
  181.         -rmargin ${cm}c
  182.     }
  183. }
  184.  
  185. # reset the state of window - get ready for the next page
  186. # remove all but the font tags, and remove all form state
  187.  
  188. proc HMreset_win {win {clear 1}} {
  189.     upvar #0 HM$win var
  190.     if $clear {
  191.         eval $win mark unset [$win mark names]
  192.         $win delete 0.0 end
  193.         # configure the text insertion point
  194.         $win mark set $var(S_insert) 1.0
  195.     }
  196.     $win tag configure hr -tabs [winfo width $win]
  197.  
  198.     Head_ResetColors $win
  199.     Form_Reset $win
  200.     catch {Table_Reset $win}
  201.     Image_Reset $win
  202.     HMinit_state $win
  203.     return HM$win
  204. }
  205.  
  206. # initialize the window's state array
  207. # Parameters beginning with S_ are NOT reset
  208. #  adjust_size:        global font size adjuster
  209. #  unknown:        character to use for unknown entities
  210. #  tab:            tab stop (in cm)
  211. #  stop:        enabled to stop processing
  212. #  update:        how many tags between update calls
  213. #  tags:        number of tags processed so far
  214. #  symbols:        Symbols to use on un-ordered lists
  215.  
  216. proc HMinit_state {win} {
  217.     upvar #0 HM$win var
  218.     array set tmp [array get var S_*]
  219.     catch {unset var}
  220.     array set var {
  221.         stop 0
  222.         tags 0
  223.         fill 0
  224.         list list
  225.         listtags ""
  226.         S_adjust_size 0
  227.         S_tab 1.0
  228.         S_unknown \xb7
  229.         S_update 10
  230.         S_symbols O*=+-o\xd7\xb0>:\xb7
  231.         S_insert Insert
  232.     }
  233.     array set var [array get tmp]
  234. }
  235.  
  236. # alter the parameters of the text state
  237. # this allows an application to over-ride the default settings
  238. # it is called as: HMset_state -param value -param value ...
  239.  
  240. array set HMparam_map {
  241.     -update S_update
  242.     -tab S_tab
  243.     -unknown S_unknown
  244.     -stop stop
  245.     -size S_adjust_size
  246.     -symbols S_symbols
  247.     -insert S_insert
  248. }
  249.  
  250. proc HMset_state {win args} {
  251.     upvar #0 HM$win var
  252.     global HMparam_map
  253.     set bad 0
  254.     if {[catch {array set params $args}]} {return 0}
  255.     foreach i [array names params] {
  256.         incr bad [catch {set var($HMparam_map($i)) $params($i)}]
  257.     }
  258.     return [expr $bad == 0]
  259. }
  260.  
  261. ############################################
  262. # manage the display of html
  263.  
  264. # HMrender gets called for every html tag
  265. #   win:   The name of the text widget to render into
  266. #   tag:   The html tag (in arbitrary case)
  267. #   not:   a "/" or the empty string
  268. #   param: The un-interpreted parameter list
  269. #   text:  The plain text until the next html tag
  270.  
  271. proc HMrender {win tag not param text} {
  272.     global HMtag_map BreakMap
  273.     upvar #0 HM$win var
  274.     if {$var(stop)} return
  275.  
  276.     set tag [string tolower $tag]
  277.     set text [HMmap_esc $text]
  278.     incr var(tags)            ;# Counter used for UID's
  279.  
  280.     # Divert table contents
  281.     if [info exists var(tableHandler)] {
  282.         if [catch {eval $var(tableHandler) {$tag $not $param text}} err] {
  283.             Stderr $err
  284.         }
  285.         return
  286.     }
  287.  
  288.     # adjust (push or pop) tag state
  289.     catch {HMstack$not $win $HMtag_map($tag)}
  290.  
  291.     # to fill or not to fill
  292.     set fill [lindex $var(fill) end]
  293.     if $fill {
  294.         set text [HMzap_white $text]
  295.     }
  296.     # Break the line, if necessary
  297.     if [info exists BreakMap($tag)] {
  298.         if $fill {
  299.         set text [string trimleft $text]
  300.         }
  301.         if ![info exists var(newline)] {
  302.         dputs "newline"
  303.         Text_Insert $win $var(S_insert) \n "space $var(listtags)"
  304.         set var(newline) 1
  305.         set var(trimspace) 1
  306.         catch {unset var(Tbr)}    ;# br hack
  307.         }
  308.     }
  309.  
  310.     # generic mark hook to support the editor
  311.     if [catch {HMmark $win $tag $not $param text} err] {
  312.         dputs "HMmark $err"
  313.     }
  314.  
  315.     # do any special tag processing
  316.  
  317.     if [catch {HMtag_$not$tag $win $param text} msg] {
  318.         if {[info command HMtag_$not$tag] != {}} {        ;# dputs
  319.         global errorInfo                ;# dputs
  320.         Exmh_Debug "HMtag_$not$tag: $errorInfo"    ;# dputs
  321.         }                            ;# dputs
  322.     }
  323.     if {$fill && [info exists var(trimspace)]} {
  324.         set text [string trimleft $text]
  325.     }
  326.     if {[string compare $text ""] != 0} {
  327.         catch {unset var(trimspace)}
  328.         catch {unset var(newline)}
  329.     }
  330.  
  331.     # HMcurrent_tags has side effects.  Call even if text is empty
  332.     set tags [HMcurrent_tags $win]
  333.  
  334.     # Fix here to do something better with   which is \0xa0
  335.  
  336.     Text_Insert $win $var(S_insert) $text $tags
  337.  
  338.     # We need to do an update every so often to insure interactive response.
  339.     # This can cause us to re-enter the event loop, and cause recursive
  340.     # invocations of HMrender, so we need to be careful.
  341.     if {!($var(tags) % $var(S_update))} {
  342.         update
  343.     }
  344. }
  345.  
  346. # html tags requiring special processing
  347. # Procs of the form HMtag_<tag> or HMtag_</tag> get called just before
  348. # the text for this tag is displayed.  These procs are called inside a 
  349. # "catch" so it is OK to fail.
  350. #   win:   The name of the text widget to render into
  351. #   param: The un-interpreted parameter list
  352. #   text:  A pass-by-reference name of the plain text until the next html tag
  353. #          Tag commands may change this to affect what text will be inserted
  354. #          next.
  355.  
  356. # A pair of pseudo tags are added automatically as the 1st and last html
  357. # tags in the document.  The default is <HMstart> and </HMstart>.
  358. # Append enough blank space at the end of the text widget while
  359. # rendering so HMgoto can place the target near the top of the page,
  360. # then remove the extra space when done rendering.
  361.  
  362. proc !HMtag_hmstart {win param text} {
  363.     upvar #0 HM$win var
  364.     $win mark gravity $var(S_insert) left
  365.     $win insert end "\n " last
  366.     $win mark gravity $var(S_insert) right
  367. }
  368.  
  369. proc !HMtag_/hmstart {win param text} {
  370.     $win delete last.first end
  371. }
  372.  
  373. # put the document title in the window banner, and remove the title text
  374. # from the document
  375.  
  376. proc !HMtag_title {win param text} {
  377.     upvar $text data
  378.     dputs $data
  379.     wm title [winfo toplevel $win] $data
  380.     wm iconname [winfo toplevel $win] $data
  381.     set data ""
  382. }
  383.  
  384.  
  385. proc HMtag_br {win param text} {
  386.     upvar #0 HM$win var
  387.     # Insert the newline without the "space" tag to preserve
  388.     # the surrounding node's tag range
  389.     Text_Insert $win insert \n $var(inserttags)
  390.     set var(newline) 1
  391.     set var(trimspace) 1
  392.     # Patch up the spacing tag on the previous line
  393.     Text_TagAdd $win abovebr "$var(S_insert) -1 line linestart"  "$var(S_insert) -1 line lineend"
  394.     # Set up spacing tag for the next line
  395.     HMstack $win "Tbr belowbr"
  396. }
  397.  
  398. # list element tags
  399.  
  400. # <ol type= start=>
  401. # type is 1, A, a, i, I to indicate numbering style
  402. # start is a number, always decimal, giving starting number of first <li>
  403.  
  404. proc HMtag_ol {win param text} {
  405.     upvar #0 HM$win var
  406.     set start 1
  407.     if [HMextract_param $param start] {
  408.     set var(count$var(level)) [incr start -1]
  409.     } else {
  410.     set var(count$var(level)) 0
  411.     }
  412.     set type 1
  413.     if [HMextract_param $param type] {
  414.     set var(oltype$var(level)) $type
  415.     } else {
  416.     catch {unset var(oltype$var(level)}
  417.     }
  418.     catch {unset var(menu$var(level))}
  419.     HMlist_open $win $param ol $var(level)
  420. }
  421. proc HMtag_/ol {win param text} {
  422.     catch {unset var(menu$var(level))}    ;# See Mark_ReadTags ol hack
  423.     HMlist_close $win
  424. }
  425. proc HMtag_ul {win param text} {
  426.     upvar #0 HM$win var
  427.     catch {unset var(count$var(level))}
  428.     catch {unset var(menu$var(level))}
  429.     HMlist_open $win $param ul $var(level)
  430. }
  431. proc HMtag_/ul {win param text} {
  432.     HMlist_close $win
  433. }
  434.  
  435. proc HMtag_menu {win param text} {
  436.     upvar #0 HM$win var
  437.     set var(menu$var(level)) ->
  438.     HMlist_open $win $param menu $var(level)
  439. }
  440. proc HMtag_/menu {win param text} {
  441.     upvar #0 HM$win var
  442.     HMlist_close $win
  443. }
  444.     
  445. proc HMtag_dir {win param text} {
  446.     upvar #0 HM$win var
  447.     catch {unset var(count$var(level))}
  448.     catch {unset var(menu$var(level))}
  449.     HMlist_open $win $param dir $var(level)
  450. }
  451. proc HMtag_/dir {win param text} {
  452.     upvar #0 HM$win var
  453.     HMlist_close $win
  454. }
  455.     
  456. proc HMtag_dl {win param text} {
  457.     upvar #0 HM$win var
  458.     catch {unset var(count$var(level))}
  459.     catch {unset var(menu$var(level))}
  460.     HMlist_open $win $param dl $var(level)
  461.     HMstack $win [list dlevel $var(level)]
  462. }
  463. proc HMtag_/dl {win param text} {
  464.     upvar #0 HM$win var
  465.     HMstack/ $win [list dlevel {}]
  466.     HMlist_close $win
  467. }
  468.     
  469. proc HMtag_dt {win param text} {
  470.     upvar #0 HM$win var
  471.     upvar $text data
  472.  
  473.     if ![info exists var(dlevel)] {
  474.         return    ;# No <dl> tag
  475.     }
  476.     set dlevel [lindex $var(dlevel) end]
  477.     if {$dlevel == {}} {
  478.         return
  479.     }
  480.     # Normally var(level) is 1 inside a list, but the out-dented terms
  481.     # need to be at indent0
  482.  
  483.     if {$var(level) > $dlevel} {
  484.         set var(indent) [lreplace $var(indent) end end]
  485.         incr var(level) -1
  486.     }
  487.     Text_Insert $win $var(S_insert) "$data" \
  488.         "indent$var(level) $var(font) $var(listtags)"
  489.     catch {unset var(newline)}
  490.     catch {unset var(trimspace)}
  491.     set data {}
  492. }
  493. proc HMtag_dd {win param text} {
  494.     upvar #0 HM$win var
  495.     if ![info exists var(dlevel)] {
  496.         return    ;# No <dl> tag
  497.     }
  498.     set dlevel [lindex $var(dlevel) end]
  499.     if {$dlevel == {}} {
  500.         return
  501.     }
  502.     # assert var(level) is equal var(dlevel) because of tag_dt,
  503.     # and that this is one-less than normal for lists
  504.     if {$var(level) == $dlevel} {
  505.         lappend var(indent) 1
  506.         incr var(level)
  507.     }
  508. }
  509.  
  510. proc HMtag_li {win param text} {
  511.     upvar #0 HM$win var
  512.     set level $var(level)
  513.     incr level -1
  514.     set x [string index $var(S_symbols)+-+-+-+-" $level]
  515.     catch {set x [incr var(count$level)]}
  516.     catch {set x [HMol_number $var(oltype$level) $var(count$level)]}
  517.     catch {set x $var(menu$level)}
  518.     Text_Insert $win $var(S_insert) \t$x\t "mark indent$level $var(font) $var(listtags)"
  519.     catch {unset var(newline)}
  520. }
  521. proc HMol_number {type count} {
  522.     switch -- $type {
  523.         A -
  524.         a {
  525.         # Count a, b, c, ..., z, aa, ab, ac, ..., az, ba, bb, bc
  526.         # which is odd, because in the '1's position a means 0,
  527.         # but in the '10's and '100's position a means 1...
  528.         # (imagine lists count from 0: a => 0, but aa => 10) 
  529.         scan $type %c A
  530.         set result ""
  531.         while {$count > 0} {
  532.             set result [format %c [expr $A + (($count-1) % 26)]]$result
  533.             set count [expr ($count-1) / 26]
  534.         }
  535.         return $result
  536.         }
  537.         i -
  538.         I {
  539.         # Count with roman numbers
  540.         # i, ii, iii, iv, v, vi, viii, ix, x
  541.         set one I ; set five V ; set ten X
  542.         set result ""
  543.         while {$count > 0} {
  544.             set frac [expr $count % 10]
  545.             switch $frac {
  546.             1 {set result $one$result}
  547.             2 {set result $one$one$result}
  548.             3 {set result $one$one$one$result}
  549.             4 {set result $one$five$result}
  550.             5 {set result $five$result}
  551.             6 {set result $five$one$result}
  552.             7 {set result $five$one$one$result}
  553.             8 {set result $five$one$one$one$result}
  554.             9 {set result $one$ten$result}
  555.             }
  556.             set count [expr $count / 10]
  557.             switch $one {
  558.             I {set one X ; set five L ; set ten C}
  559.             X {set one C ; set five D ; set ten M}
  560.             C {set one M ; set five ? ; set ten !}
  561.             default {set one ! ; set five # ; set ten @}
  562.             }
  563.         }
  564.         if {$type == "i"} {
  565.             return [string tolower $result]
  566.         } else {
  567.             return $result
  568.         }
  569.         }
  570.         1 -
  571.         default { 
  572.         return $count
  573.         }
  574.     }
  575. }
  576. array set HMromanI {
  577.     I    1
  578.     V    5
  579.     X    10
  580.     L    50
  581.     C    100
  582.     D    500
  583.     M    1000
  584. }
  585. array set HMromani {
  586.     i    1
  587.     v    5
  588.     x    10
  589.     l    50
  590.     c    100
  591.     d    500
  592.     m    1000
  593. }
  594.  
  595. # The Tspace tag is used for inter-line spacing.
  596. # The listtags variable is used to label newlines w/in lists
  597.  
  598. proc HMlist_open {win param ltag level} {
  599.     if {[HMextract_param $param compact] ||
  600.     [string compare $ltag "dl"] == 0} {
  601.     set space compact
  602.     } else {
  603.     set space hspacep
  604.     }
  605.     set x [list Tspace $space listtags $space]
  606.     lappend x listtags [string trim "H:$ltag=[incr level] $param"]
  607.     HMstack $win $x
  608. }
  609. # The catch protects against extra close tags
  610. proc HMlist_close {win} {
  611.     if ![catch {HMstack/ $win {listtags {}}}] {
  612.     catch {HMstack/ $win {Tspace {} listtags {}}}
  613.     }
  614. }
  615.  
  616. # Manage hypertext "anchor" links.  A link can be either a source (href)
  617. # a destination (name) or both.  If its a source, register it via a callback,
  618. # and set its default behavior.  If its a destination, check to see if we need
  619. # to go there now, as a result of a previous HMgoto request.  If so, schedule
  620. # it to happen with the closing </a> tag, so we can highlight the text up to
  621. # the </a>.
  622.  
  623. proc HMtag_a {win param text} {
  624.     upvar #0 HM$win var
  625.     dputs $param
  626.  
  627.     # Clean up any state from unclosed <a> tags
  628.     HMtag_/a $win {} {}
  629.  
  630.     # a source
  631.  
  632.     if {[HMextract_param $param href] && [info exists href]} {
  633.         set var(Lref) $href
  634.         HMlink_setup $win "a $param"
  635.         set var(Tlink) link
  636.     }
  637.  
  638.     # a frame specifier
  639.  
  640.     if {[HMextract_param $param target] && [info exists target]} {
  641.         set var(Fref) $target
  642.     }
  643.  
  644.     # a destination
  645.  
  646.     if {[HMextract_param $param name] && [info exists name]} {
  647.         $win mark set N:$name "$var(S_insert) - 1 chars"
  648.         $win mark gravity N:$name left
  649.         set var(Tanchor) anchor
  650.         if {[info exists var(goto)] && $var(goto) == $name} {
  651.             dputs "scheduling move to target $name"
  652.             unset var(goto)
  653.             set var(going) $name
  654.         }
  655.     }
  656. }
  657.  
  658. # The application should call here with the fragment name
  659. # to cause the display to go to this spot.
  660. # If the target exists, go there (and do the callback),
  661. # otherwise schedule the goto to happen when we see the reference.
  662.  
  663. proc HMgoto {win where {callback HMwent_to}} {
  664.     upvar #0 HM$win var
  665.     dputs "looking to goto $where"
  666.     if {![catch {$win index N:$where} ix]} {
  667.         dputs "Found goto target - going there"
  668.         scan $ix %d line
  669.         scan [$win index end] %d lastline
  670.         $win yview moveto [expr $line.0 / $lastline.0]
  671. #        $win see N:$where
  672.         update
  673.         eval $callback $win [list $where]
  674.         return 1
  675.     } else {
  676.         dputs "Target not found, queued"
  677.         set var(goto) $where
  678.         return 0
  679.     }
  680. }
  681.  
  682. # We actually got to the spot, so highlight it!
  683. # This should/could be replaced by the application
  684. # We'll flash it orange a couple of times.
  685.  
  686. proc HMwent_to {win where {count 0} {color orange}} {
  687.     upvar #0 HM$win var
  688.     if {$count > 5} return
  689.     catch {$win tag configure N:$where -foreground $color}
  690.     update
  691.     after 200 [list HMwent_to $win $where [incr count] \
  692.                 [expr {$color=="orange" ? "" : "orange"}]]
  693. }
  694.  
  695. proc HMtag_/a {win param text} {
  696.     upvar #0 HM$win var
  697.     catch {unset var(Lref)}
  698.     catch {unset var(Fref)}
  699.     catch {unset var(Tlink)}
  700.     catch {unset var(Tanchor)}
  701.     catch {unset var(T,a)}
  702.  
  703.     # goto this link, then invoke the call-back.
  704.  
  705.     if {[info exists var(going)]} {
  706.         $win yview N:$var(going)
  707.         update
  708.         HMwent_to $win $var(going)
  709.         unset var(going)
  710.     }
  711. }
  712.  
  713.  
  714. # Sample hypertext link callback routine - should be replaced by app
  715. # This proc is called once for each <A> tag.
  716. # Applications can overwrite this procedure, as required, or
  717. # replace the HMevents array
  718. #   win:   The name of the text widget to render into
  719. #   href:  The HREF link for this <a> tag.
  720.  
  721. array set HMevents {
  722.     Enter    {-borderwidth 2 -relief raised }
  723.     Leave    {-borderwidth 2 -relief flat }
  724.     1        {-borderwidth 2 -relief sunken}
  725.     ButtonRelease-1    {-borderwidth 2 -relief flat}
  726. }
  727.  
  728. # extract a value from parameter list (this needs a re-do)
  729. # returns "1" if the keyword is found, "0" otherwise
  730. #   param:  A parameter list.  It should alredy have been processed to
  731. #           remove any entity references
  732. #   key:    The parameter name
  733. #   val:    The variable to put the value into (use key as default)
  734.  
  735. proc HMextract_param {param key {val ""}} {
  736.  
  737.     if {$val == ""} {
  738.         upvar $key result
  739.     } else {
  740.         upvar $val result
  741.     }
  742.     dputs looking for $key in <$param>
  743.     set ws " \t\n\r"
  744.  
  745.     # look for name=value combinations.  Either (') or (") are valid delimeters
  746.     if {
  747.       [regsub -nocase [format {.*[%s'"]+%s[%s]*=[%s]*"([^"]*).*} $ws $key $ws $ws] " $param" {\1} value] ||
  748.       [regsub -nocase [format {.*[%s'"]+%s[%s]*=[%s]*'([^']*).*} $ws $key $ws $ws] " $param" {\1} value] ||
  749.       [regsub -nocase [format {.*[%s'"]+%s[%s]*=[%s]*([^%s]+).*} $ws $key $ws $ws $ws] " $param" {\1} value] } {
  750.         set result $value
  751.         dputs $key -> $value
  752.         return 1
  753.     }
  754.  
  755.     # now look for valueless names
  756.     # I should strip out name=value pairs, so we don't end up with "name"
  757.     # inside the "value" part of some other key word - some day
  758.     
  759.     set bad \[^a-zA-Z\]+
  760.     if {[regexp -nocase  "$bad$key$bad" -$param-]} {
  761.         dputs got $key
  762.         return 1
  763.     } else {
  764.         dputs Nope
  765.         return 0
  766.     }
  767. }
  768.  
  769. # These next two routines manage the display state of the page.
  770.  
  771. # Push or pop tags to/from stack.
  772. # Each orthogonal text property has its own stack, stored as a list.
  773. # The current (most recent) tag is the last item on the list.
  774. # HMstack pushes, and HMstack/ pops
  775.  
  776. proc HMstack {win list} {
  777.     upvar #0 HM$win var
  778.     foreach {stack value} $list {
  779.     lappend var($stack) $value
  780.     }
  781. }
  782. proc HMstack/ {win list} {
  783.     upvar #0 HM$win var
  784.     foreach {stack value} $list {
  785.     set var($stack) [lreplace $var($stack) end end]
  786.     }
  787. }
  788.  
  789. # extract set of current text tags
  790. # tags starting with T map directly to text tags, all others are
  791. # handled specially.  There is an application callback, HMset_font
  792. # to allow the application to do font error handling
  793.  
  794. proc HMcurrent_tags {win} {
  795.     global HMsize_map
  796.     upvar #0 HM$win var
  797.     set font font
  798.     foreach i {family size weight style} {
  799.         set $i [lindex $var($i) end]
  800.         append font :[set $i]
  801.     }
  802.     set xfont [HMx_font $family $HMsize_map($size) $weight $style $var(S_adjust_size)]
  803.     HMset_font $win $font $xfont
  804.     set indent [llength $var(indent)]
  805.     incr indent -1
  806. #    if {$indent < 0} {
  807. #        set var(indent) {}
  808. #        set indent 0
  809. #    }
  810.     lappend tags $font indent$indent
  811.     foreach tag [array names var T*] {
  812.         set x [lindex $var($tag) end]
  813.         if [string length $x] {
  814.             lappend tags $x
  815.         }
  816.     }
  817.     set var(font) $font
  818.     set var(xfont) [$win tag cget $font -font]
  819.     set var(level) $indent
  820.     set var(inserttags) $tags
  821.     return $tags
  822. }
  823.  
  824. # allow the application to do do better font management
  825. # by overriding this procedure
  826.  
  827. proc !HMset_font {win tag font} {
  828.     catch {$win tag configure $tag -font $font} msg
  829. }
  830.  
  831. # generate an X font name
  832. proc HMx_font {family size weight style {adjust_size 0}} {
  833.     catch {incr size $adjust_size}
  834.     return "-*-$family-$weight-$style-normal-*-*-${size}0-*-*-*-*-*-*"
  835. }
  836.  
  837. ############################################
  838. # Turn HTML into TCL commands
  839. #   html    A string containing an html document
  840. #   cmd        A command to run for each html tag found
  841. #   start    The name of the dummy html start/stop tags
  842.  
  843. proc HMparse_html {html {cmd HMtest_parse} {start hmstart}} {
  844.     regsub -all \{ $html {\&ob;} html
  845.     regsub -all \} $html {\&cb;} html
  846.     regsub -all {\\} $html {\&bsl;} html
  847.     set w " \t\r\n"    ;# white space
  848.     proc HMcl x {return "\[$x\]"}
  849.     set exp <(/?)([HMcl ^$w>]+)[HMcl $w]*([HMcl ^>]*)>
  850.     set sub "\}\n$cmd {\\2} {\\1} {\\3} \{"
  851.     regsub -all $exp $html $sub html
  852.     eval "$cmd {$start} {} {} \{$html\}"
  853.     eval "$cmd {$start} / {} {}"
  854. }
  855.  
  856. proc HMtest_parse {command tag slash text_after_tag} {
  857.     puts "==> $command $tag $slash $text_after_tag"
  858. }
  859.  
  860. # Convert multiple white space into a single space
  861.  
  862. proc HMzap_white {data} {
  863.     regsub -all "\[ \t\r\n\]+" $data " " data
  864.     return $data
  865. }
  866.  
  867. # find HTML escape characters of the form &xxx;
  868.  
  869. proc HMmap_esc {text} {
  870.     if {![regexp & $text]} {return $text}
  871.     regsub -all {([][$\\])} $text {\\\1} new
  872.     regsub -all {&#([0-9][0-9]?[0-9]?);?} \
  873.         $new {[format %c [scan \1 %d tmp;set tmp]]} new
  874.     regsub -all {&([a-zA-Z]+)(;?)} $new {[HMdo_map \1 \\\2 ]} new
  875.     return [subst $new]
  876. }
  877. # convert an HTML escape sequence into character
  878. proc HMdo_map {text {semi {}}} {
  879.     global HMesc_map
  880.     set result &$text$semi
  881.     catch {set result $HMesc_map($text)}
  882.     return $result
  883. }
  884.  
  885. # Encode special characters in the form &xxx;
  886. proc HMmap_code {text} {
  887.     if {![regexp \[<>&\x80-\xff\] $text]} {return $text}
  888.     regsub -all {([][$\\])} $text {\\\1} new
  889.     regsub -all (\[<>&\x80-\xff\]) $new {[HMdo_code \\\1]} new
  890.     return [subst $new]
  891. }
  892. proc HMdo_code {text} {
  893.     global HMcode_map
  894.     if [info exists HMcode_map($text)] {
  895.         return &$HMcode_map($text)\;
  896.     } else {
  897.         return $text
  898.     }
  899. }
  900.  
  901. # table of escape characters (ISO latin-1 esc's are in a different table)
  902.  
  903. array set HMesc_map {
  904.    lt <   gt >   amp &   quot \"  bsl \\  
  905.    ob \x7b   cb \x7d   nbsp \xa0
  906. }
  907. # Some folks like capitals, which are non-standard
  908. array set HMesc_map {
  909.    LT <   GT >   AMP &   QUOT \"   NBSP \xa0
  910. }
  911. #############################################################
  912. # ISO Latin-1 escape codes
  913.  
  914. array set HMesc_map {
  915.     nbsp \xa0 iexcl \xa1 cent \xa2 pound \xa3 curren \xa4
  916.     yen \xa5 brvbar \xa6 sect \xa7 uml \xa8 copy \xa9
  917.     ordf \xaa laquo \xab not \xac shy \xad reg \xae
  918.     hibar \xaf deg \xb0 plusmn \xb1 sup2 \xb2 sup3 \xb3
  919.     acute \xb4 micro \xb5 para \xb6 middot \xb7 cedil \xb8
  920.     sup1 \xb9 ordm \xba raquo \xbb frac14 \xbc frac12 \xbd
  921.     frac34 \xbe iquest \xbf Agrave \xc0 Aacute \xc1 Acirc \xc2
  922.     Atilde \xc3 Auml \xc4 Aring \xc5 AElig \xc6 Ccedil \xc7
  923.     Egrave \xc8 Eacute \xc9 Ecirc \xca Euml \xcb Igrave \xcc
  924.     Iacute \xcd Icirc \xce Iuml \xcf ETH \xd0 Ntilde \xd1
  925.     Ograve \xd2 Oacute \xd3 Ocirc \xd4 Otilde \xd5 Ouml \xd6
  926.     times \xd7 Oslash \xd8 Ugrave \xd9 Uacute \xda Ucirc \xdb
  927.     Uuml \xdc Yacute \xdd THORN \xde szlig \xdf agrave \xe0
  928.     aacute \xe1 acirc \xe2 atilde \xe3 auml \xe4 aring \xe5
  929.     aelig \xe6 ccedil \xe7 egrave \xe8 eacute \xe9 ecirc \xea
  930.     euml \xeb igrave \xec iacute \xed icirc \xee iuml \xef
  931.     eth \xf0 ntilde \xf1 ograve \xf2 oacute \xf3 ocirc \xf4
  932.     otilde \xf5 ouml \xf6 divide \xf7 oslash \xf8 ugrave \xf9
  933.     uacute \xfa ucirc \xfb uuml \xfc yacute \xfd thorn \xfe
  934.     yuml \xff
  935. }
  936.  
  937. foreach x [array names HMesc_map] {
  938.     set HMcode_map($HMesc_map($x)) $x
  939. }
  940. set HMcode_map(\\) \\x5c
  941.  
  942.  
  943. # do x-www-urlencoded character mapping
  944. # The spec says: "non-alphanumeric characters are replaced by '%HH'"
  945.  
  946. set HMalphanumeric    a-zA-Z0-9    ;# definition of alphanumeric character class
  947. for {set i 1} {$i <= 256} {incr i} {
  948.     set c [format %c $i]
  949.     if {![string match \[$HMalphanumeric\] $c]} {
  950.         set HMform_map($c) %[format %.2x $i]
  951.     }
  952. }
  953.  
  954. # These are handled specially
  955. array set HMform_map {
  956.     " " +   \n %0d%0a
  957. }
  958.  
  959. # 1 leave alphanumerics characters alone
  960. # 2 Convert every other character to an array lookup
  961. # 3 Escape constructs that are "special" to the tcl parser
  962. # 4 "subst" the result, doing all the array substitutions
  963.  
  964. proc HMmap_reply {string} {
  965.     global HMform_map HMalphanumeric
  966.     regsub -all \[^$HMalphanumeric\] $string {$HMform_map(&)} string
  967.     regsub -all \n $string {\\n} string
  968.     regsub -all \t $string {\\t} string
  969.     regsub -all {[][{})\\]\)} $string {\\&} string
  970.     return [subst $string]
  971. }
  972.  
  973. # There is a bug in the tcl library focus routines that prevents focus
  974. # from every reaching an un-viewable window.  Use our *own*
  975. # version of the library routine, until the bug is fixed, make sure we
  976. # over-ride the library version, and not the otherway around
  977.  
  978. if {0} {
  979.     
  980.     # This bug is related to tabbing between embedded windows in a text
  981.     # widget, which is not important to exmh.  Defining this procedure
  982.     # can cause loops in the Tcl auto_load mechanism.
  983.  
  984.     auto_load tkFocusOK
  985.      proc tkFocusOK w {
  986.     set code [catch {$w cget -takefocus} value]
  987.     if {($code == 0) && ($value != "")} {
  988.     if {$value == 0} {
  989.         return 0
  990.     } elseif {$value == 1} {
  991.         return 1
  992.     } else {
  993.         set value [uplevel #0 $value $w]
  994.         if {$value != ""} {
  995.         return $value
  996.         }
  997.     }
  998.     }
  999.     set code [catch {$w cget -state} value]
  1000.     if {($code == 0) && ($value == "disabled")} {
  1001.     return 0
  1002.     }
  1003.     regexp Key|Focus "[bind $w] [bind [winfo class $w]]"
  1004.     }
  1005. }
  1006.  
  1007. # simple stuff to support interactive variable tracing
  1008. # Module prefix is T
  1009. #  - print value any time global variable changes
  1010.  
  1011. # Basic Usage:
  1012. #  T            print variables with traces
  1013. #  T <x>        put a write trace on variable <x>
  1014. #  X <x>        remove trace on variable <x>
  1015.  
  1016. # print traced variable (standard trace function)
  1017.  
  1018. proc Tprint {n1 n2 op} {
  1019.     upvar $n1 value
  1020.  
  1021.     set level [expr [info level] - 1]
  1022.     if {$level > 0} {
  1023.         set proc [lindex [info level $level] 0]
  1024.     } else {
  1025.         set proc Toplevel
  1026.     }
  1027.     if {$n2 == ""} {
  1028.         puts "TRACE: $n1 = $value (in $proc)"
  1029.     } else {
  1030.         puts "TRACE: ${n1}($n2) = $value($n2) (in $proc)"
  1031.     }
  1032. }
  1033.  
  1034. # set [or query] a global variable trace
  1035. proc T {{_x_ "?"} {op w} {function Tprint}} {
  1036.     global $_x_ Traces
  1037.     if {$_x_ == "?"} {
  1038.         Stderr "Current traces:"
  1039.         catch "parray Traces"
  1040.     } elseif {[info exists Traces($_x_)]} {
  1041.         Stderr "Replacing existing trace for $_x_"
  1042.     } else {
  1043.         Stderr "Setting trace for $_x_"
  1044.         set Traces($_x_) $op
  1045.     }
  1046.     trace variable $_x_ $op $function
  1047. }
  1048.  
  1049. # delete all traces on a variable
  1050.  
  1051. proc X {{_x_ ?}} {
  1052.     global $_x_ Traces
  1053.     if {$_x_ == "?"} {
  1054.         Stderr "Usage: X <var_name> (remove trace on var_name>"
  1055.         return ""
  1056.     }
  1057.     catch "unset Traces($_x_)"
  1058.     foreach trace [trace vinfo $_x_] {
  1059.         Stderr "Trace remove: $_x_ $trace"
  1060.         eval "trace vdelete $_x_ $trace"
  1061.     }
  1062. }
  1063. # simple puts style debugging support
  1064. # Module Prefix is "D"
  1065. # The array Dholds all of the state info for the debugger
  1066. # Interface:
  1067. #   Don:    turn on debugging
  1068. #     D(print): list of patterns that cause printing
  1069. #     D(break): list of patterns that cause break points
  1070. #   Doff:    turn off debugging
  1071.  
  1072. proc Dtrace { args } {
  1073.     global D
  1074.     eval lappend D(print) $args
  1075. }
  1076. proc Don {} {
  1077.     global D
  1078.     foreach x {print break} {
  1079.         if ![info exists D($x)] {set D($x) {} }
  1080.     }
  1081. #    Stderr "Debugging enabled"
  1082.     proc dputs {args} {
  1083.         global D
  1084.         set level [expr [info level] - 1]
  1085.         set caller toplevel
  1086.         catch {set caller [lindex [info level $level] 0]}
  1087.         foreach i $D(print) {
  1088.             if {[string match $i $caller]} {
  1089.                 Stderr "$caller: $args"
  1090.                 break;
  1091.             }
  1092.         }
  1093.         if {[string match $D(break) $caller]} {
  1094.             Deval
  1095.         }
  1096.     }
  1097. }
  1098.  
  1099. proc Doff {} {
  1100.     global D
  1101. #    Stderr "Debugging disabled"
  1102.     proc dputs {args} {}
  1103. }
  1104.  
  1105. # read-print-eval loop for debugging
  1106.  
  1107. proc Deval {} {
  1108.     set maxlevel [expr [info level] -1]
  1109.     set level $maxlevel
  1110.     set ok 1
  1111.     Dshow $level
  1112.     while {$ok} {
  1113.         puts -nonewline stderr "#$level: "
  1114.         gets stdin line
  1115.         while {![info complete $line]} {
  1116.             puts -nonewline stderr "? "
  1117.             append line \n[gets stdin]
  1118.         }
  1119.         switch -- $line {
  1120.             +    {if {$level < $maxlevel} {Dshow [incr level]}}
  1121.             -    {if {$level > 0} {Dshow [incr level -1]}}
  1122.             C   {set ok 0}
  1123.             ?   {Dshow $level}
  1124.             G    {
  1125.                 catch { uplevel #0 [lrange $line 1 end]} result
  1126.                 Stderr $result
  1127.             }
  1128.             W    {
  1129.                 for {set l $level} {$l > 0} {incr l -1}  {
  1130.                     Dshow $l
  1131.                 }
  1132.             }
  1133.             default {
  1134.                 catch { uplevel #$level $line } result
  1135.                 Stderr $result
  1136.             }
  1137.         }
  1138.     }
  1139.     Stderr "Resuming Execution"
  1140. }
  1141.  
  1142. # display state of this stack level
  1143.  
  1144. proc Dshow {level} {
  1145.     if {$level <=0} {
  1146.         Stderr "At top level"
  1147.         return
  1148.     }
  1149.     set info [info level $level]
  1150.     set proc [lindex $info 0]
  1151.     Stderr "Procedure $proc {[info args $proc]}"
  1152.     set index 0
  1153.     foreach arg [info args $proc] {
  1154.         Stderr "\t$arg = [lindex $info [incr index]]"
  1155.     }
  1156.     set locals [uplevel #$level "info locals"]
  1157.     set all [uplevel #$level "info vars"]
  1158.     Stderr "\tlocals: $locals"
  1159.     foreach i $locals {set local($i) 1}
  1160.     set globals ""
  1161.     foreach i $all {
  1162.         if {![info exists local($i)]} {lappend globals $i}
  1163.     }
  1164.     Stderr "\tglobals: $globals"
  1165. }
  1166.  
  1167. proc !Dshow {current} {
  1168.   if {$current > 0} {
  1169.     set info [info level $current]
  1170.     set proc [lindex $info 0]
  1171.     Stderr "$current: Procedure $proc {[info args $proc]}"
  1172.     set index 0
  1173.     foreach arg [info args $proc] {
  1174.       Stderr "\t$arg = [lindex $info [incr index]]"
  1175.     }
  1176.   } else {
  1177.     Stderr "Top level"
  1178.   }
  1179. }
  1180.  
  1181. # for convenience
  1182.  
  1183. proc ? {} {
  1184.     global errorInfo
  1185.     Stderr $errorInfo
  1186. }
  1187. if {[info commands dputs]  == ""} {
  1188.     Doff
  1189. }
  1190.